﻿#include "sound.h"
#include <fstream>

using namespace anip::audio;

vector<SoundGroup*> Groups;
SoundGroup* MainGroup = new SoundGroup(L"Background");

SoundGroup::~SoundGroup()
{
    for (auto s : sounds)
        delete s;
}

Sound::~Sound()
{
    delete _wave;
    if (_play != nullptr)
        delete _play;
}

Sound* Sound::create(const string& p)
{
	if (!fs::fileExists(p)) return nullptr;

    Waveform* w = nullptr;
	if (fs::extension(p) != ".ogg"s) return nullptr;
    w = Waveform::fromOGG(p);

    auto sound = new Sound();
    sound->_path = p;
    sound->name = fs::path2ws(fs::filename(p, false));
    sound->loopOut = w->runningTime();
    sound->_wave = w;
    return sound;
}

void Sound::setVolume(double v)
{
    _volume = v;
    if (_play != nullptr)
        _play->setVolume(v);
}

void Sound::setPos(double v)
{
    if (_play != nullptr)
        _play->setTimePosition(v);
}

void Sound::instantiate()
{
    auto p = Mixer::addSound(*(_wave->instantiate()), _volume);
    // ANIP_FIXME: How to release?
}

void Sound::start()
{
    if (_play != nullptr && _play->stopped())
    {
        delete _play;
        _play = nullptr;
    }
    if (_play == nullptr)
    {
        _play = Mixer::addSound(*(_wave->instantiate()), _volume);
        if (fadeTime > 0)
        {
            _fadepoint = anip::time();
            _fade = FadeType::Go;
        }

        _play->onStop = [&](Playback&) 
        {
            if (onStop) onStop(*this);
        };
        _play->onPlaying = [&](Playback& self)
        {
            constexpr double epsilon = 0.05;

            if (fadeTime > 0)
            {
                double d = (anip::time() - _fadepoint) / fadeTime;
                d = math::clamp(d, 0.0, 1.0);
                switch (_fade)
                {
                case FadeType::Go:
                    self.setVolume(d * _volume);
                    break;
                case FadeType::Pause:
                    self.setVolume((1 - d) * _volume);
                    if (d == 1) 
                    { 
                        self.pause();
                        if (onPause) onPause(*this);
                    }
                    break;
                case FadeType::Stop:
                    self.setVolume((1 - d) * _volume);
                    if (d == 1)
                    {
                        self.stop();
                        if (onStop) onStop(*this);
                    }
                    break;
                }
            }

            if (looping)
            {
                if (self.getTimePosition() >= loopOut - epsilon)
                    self.setTimePosition(loopIn);
            }
        };
    }

   _play->setTimePosition(0);
   if (onPlay) onPlay(*this);
}

void Sound::stop()
{
    if (_play == nullptr || _play->stopped()) return;
    if (fadeTime == 0 || _play->paused())
        _play->stop();
    else
    {
        _fadepoint = anip::time();
        _fade = FadeType::Stop;
    }
}

void Sound::pause()
{
    if (_play == nullptr) return;

    if (fadeTime > 0)
    {
        _fadepoint = anip::time();
        _fade = FadeType::Pause;
    }
    else
        _play->pause();

    if (onPause) onPause(*this);
}

void Sound::unpause()
{
    if (_play == nullptr) return;
    _play->unpause();
    if (fadeTime > 0)
    {
        _fadepoint = anip::time();
        _fade = FadeType::Go;
    }
    if (onUnpause) onUnpause(*this);
}

void Sound::writeConfig()
{
	string p = fs::removeExtension(_path) + ".ini"s;
	
    ofstream s(p, ios::out);
    s << "Volume:  " << _volume << endl;
    s << "Fading:  " << fadeTime << endl;
    s << "Looping: " << looping << endl;
    s << "LoopIn:  " << loopIn << endl;
    s << "LoopOut: " << loopOut << endl << endl;
    s.close();
}

bool Sound::readConfig()
{
	string p = fs::removeExtension(_path) + ".ini"s;
	
    if (!fs::fileExists(p)) return true;

    try
    {
        ifstream s(p, ios::in);
        string prompt;
        s >> prompt; if (prompt != "Volume:") throw exception();
        s >> _volume;
        s >> prompt; if (prompt != "Fading:") throw exception();
        s >> fadeTime;
        s >> prompt; if (prompt != "Looping:") throw exception();
        s >> looping;
        s >> prompt; if (prompt != "LoopIn:") throw exception();
        s >> loopIn;
        s >> prompt; if (prompt != "LoopOut:") throw exception();
        s >> loopOut;
        s.close();
    } 
    catch (...)
    {
        return false;
    }

    return true;
}
